package ipParse

import (
	"encoding/binary"
	"errors"
	"io/ioutil"
	"log"
	"net"
	"os"
	"strings"

	"golang.org/x/text/encoding/simplifiedchinese"
)

const (
	// IndexLen 索引长度
	IndexLen = 7
	// RedirectMode1 国家的类型, 指向另一个指向
	RedirectMode1 = 0x01
	// RedirectMode2 国家的类型, 指向一个指向
	RedirectMode2 = 0x02
)

var ipv4lib []byte

func Init(libPath string) {
	//载入数据库
	if err := loadLib(libPath); err != nil {
		panic(err)
	}
	//解析数据库头
	parseLibHeader(ipv4lib)
}

func loadLib(libPath string) (err error) {
	//检测文件是否存在
	if _, err = os.Stat(libPath); err != nil && os.IsNotExist(err) { //不存在
		err = errors.New("ipv4数据库不存在")
		return
	}
	//读取文件
	var filePtr *os.File

	log.Printf("ipv4数据库从本地数据库文件 %s 打开\n", libPath)
	if filePtr, err = os.OpenFile(libPath, os.O_RDONLY, 0400); err != nil {
		return
	}
	defer filePtr.Close()

	if ipv4lib, err = ioutil.ReadAll(filePtr); err != nil {
		return
	}
	return
}

//解析文件头
func parseLibHeader(qqwryData []byte) {
	buf := qqwryData[0:8]
	start := binary.LittleEndian.Uint32(buf[:4])
	end := binary.LittleEndian.Uint32(buf[4:])
	log.Printf("ip库总数据量%d", int64((end-start)/IndexLen+1))
}

type query struct {
	IP         uint32
	TempOffset uint32 //临时变量，迭代查询时用于缓存索引进度
}

func Search(ip string) (res Res, err error) {

	q := new(query)

	//校验ip地址
	q.IP, err = validateIpv4(ip)
	if err != nil {
		return
	}
	//在索引区查找记录偏移
	offset := q.searchIndex()
	//未找到偏移说明此记录不存在
	if offset <= 0 {
		return
	}

	var country []byte
	var area []byte
	//在记录区查找记录
	//读取记录模式
	mode := q.readMode(offset + 4)
	if mode == RedirectMode1 {
		//读取国家信息
		countryOffset := q.readUInt24()
		mode = q.readMode(countryOffset)
		if mode == RedirectMode2 {
			c := q.readUInt24()
			country = q.readString(c)
			countryOffset += 4
		} else {
			country = q.readString(countryOffset)
			countryOffset += uint32(len(country) + 1)
		}
		area = q.readArea(countryOffset)
	} else if mode == RedirectMode2 {
		countryOffset := q.readUInt24()
		country = q.readString(countryOffset)
		area = q.readArea(offset + 8)
	} else {
		country = q.readString(offset + 4)
		area = q.readArea(offset + uint32(5+len(country)))
	}
	//数据库是GBK编码的，需要解码
	enc := simplifiedchinese.GBK.NewDecoder()
	//获取到信息
	countryStr, _ := enc.String(string(country))
	areaStr, _ := enc.String(string(area))
	//格式化
	res = formatRet(countryStr, areaStr)
	res.IP = ip
	return
}

//查找索引，二分法
func (q *query) searchIndex() uint32 {
	//解析头部，头部包含索引的始末位置
	header := q.readData(8, 0)
	//始末位置
	start := binary.LittleEndian.Uint32(header[:4])
	end := binary.LittleEndian.Uint32(header[4:])

	buf := make([]byte, IndexLen) //临时缓存未查找的记录区
	mid, _ip := uint32(0), uint32(0)

	//迭代查找索引区，找到此ip的索引
	for {
		//获取中间索引
		mid = q.getMidOffset(start, end)
		//获取索引详情
		buf = q.readData(IndexLen, mid)
		//获取此索引记录的ip的起始值
		_ip = binary.LittleEndian.Uint32(buf[:4])
		//如果索引只有一条
		if end-start == IndexLen {
			offset := byteToUInt32(buf[4:])
			//读取一条记录
			buf = q.readData(IndexLen)
			//判断ip是否在此条记录范围中
			if q.IP < binary.LittleEndian.Uint32(buf[:4]) {
				return offset
			}
			return 0
		}

		// 找到的比较大，向前移
		if _ip > q.IP {
			end = mid
		} else if _ip < q.IP { // 找到的比较小，向后移
			start = mid
		} else if _ip == q.IP { //恰好就是此ip
			return byteToUInt32(buf[4:])
		}
	}
}

//校验ip地址,转换ip地址类型
func validateIpv4(ipstr string) (ipUint32 uint32, err error) {
	//校验
	ip := net.ParseIP(ipstr)
	if ip == nil {
		err = errors.New("非法的ip地址")
		return
	}

	if strings.Count(ipstr, ".") != 3 {
		err = errors.New("只支持ipv4地址")
		return
	}
	//转换
	ipUint32 = binary.BigEndian.Uint32(ip.To4())
	return
}

// ReadData 从文件中读取数据
func (q *query) readData(num uint32, offset ...uint32) (rs []byte) {
	if len(offset) > 0 {
		q.TempOffset = offset[0]
	}
	//尾
	end := q.TempOffset + num
	//超出数据库索引范围
	dataNum := uint32(len(ipv4lib))
	if q.TempOffset > dataNum {
		return nil
	}
	if end > dataNum {
		end = dataNum
	}
	//取结果
	rs = ipv4lib[q.TempOffset:end]
	//重新设置偏移
	q.TempOffset = end
	return
}

// readMode 获取偏移值类型
func (q *query) readMode(offset uint32) byte {
	mode := q.readData(1, offset)
	return mode[0]
}

// readArea 读取区域
func (q *query) readArea(offset uint32) []byte {
	mode := q.readMode(offset)
	if mode == RedirectMode1 || mode == RedirectMode2 {
		areaOffset := q.readUInt24()
		if areaOffset == 0 {
			return []byte("")
		}
		return q.readString(areaOffset)
	}
	return q.readString(offset)
}

// readString 获取字符串
func (q *query) readString(offset uint32) []byte {
	q.TempOffset = offset
	data := make([]byte, 0, 30)
	buf := make([]byte, 1)
	for {
		buf = q.readData(1)
		if buf[0] == 0 {
			break
		}
		data = append(data, buf[0])
	}
	return data
}

// readUInt24
func (q *query) readUInt24() uint32 {
	buf := q.readData(3)
	return byteToUInt32(buf)
}

// 在索引区根据始末位置获取中间索引
func (q *query) getMidOffset(start uint32, end uint32) uint32 {
	records := ((end - start) / IndexLen) >> 1
	return start + records*IndexLen
}

func formatRet(countryStr, areaStr string) (res Res) {
	countryArry := strings.Split(countryStr, "|")
	areaArry := strings.Split(areaStr, "|")

	for idx, val := range countryArry {
		if val == "*" {
			continue
		}
		switch idx {
		case 0:
			res.Continent = val
		case 1:
			res.CountryCn = val
		case 2:
			res.CountryEn = val
		case 3:
			res.CountryCode = val
		case 4:
			res.RegionCode = val
		case 5:
			res.TelecomOperator = val
		}
	}

	for idx, val := range areaArry {
		if val == "*" {
			continue
		}
		switch idx {
		case 0:
			res.Province = val
		case 1:
			res.City = val
		case 2:
			res.District = val
		case 3:
			res.AdminCodeLevel1 = val
		case 4:
			res.AdminCodeLevel2 = val
		case 5:
			res.AdminCodeLevel3 = val
		case 6:
			res.Lat = val
		case 7:
			res.Lng = val
		}
	}
	return
}

// byteToUInt32 将 byte 转换为uint32
func byteToUInt32(data []byte) uint32 {
	i := uint32(data[0]) & 0xff
	i |= (uint32(data[1]) << 8) & 0xff00
	i |= (uint32(data[2]) << 16) & 0xff0000
	return i
}
